<?php
/**
 * Piwik - Open source web analytics
 *
 * @link http://piwik.org
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 *
 * @category Piwik_Plugins
 * @package Piwik_ImageGraph_StaticGraph
 */


/**
 *
 * @package Piwik_ImageGraph_StaticGraph
 */
abstract class Piwik_ImageGraph_StaticGraph_GridGraph extends Piwik_ImageGraph_StaticGraph
{
	const GRAPHIC_COLOR_KEY = 'GRAPHIC_COLOR';
	const VALUE_COLOR_KEY = 'VALUE_COLOR';
	const GRID_COLOR_KEY = 'GRID_COLOR';

	const TRUNCATION_TEXT = '...';

	const DEFAULT_TICK_ALPHA = 20;
	const DEFAULT_SERIE_WEIGHT = 0.5;
	const LEFT_GRID_MARGIN = 4;
	const BOTTOM_GRID_MARGIN = 10;
	const TOP_GRID_MARGIN_HORIZONTAL_GRAPH = 1;
	const RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH = 4;
	const OUTER_TICK_WIDTH = 5;
	const INNER_TICK_WIDTH = 0;
	const LABEL_SPACE_VERTICAL_GRAPH = 7;

	const HORIZONTAL_LEGEND_TOP_MARGIN = 5;
	const HORIZONTAL_LEGEND_LEFT_MARGIN = 10;
	const HORIZONTAL_LEGEND_BOTTOM_MARGIN = 10;
	const VERTICAL_LEGEND_TOP_MARGIN = 8;
	const VERTICAL_LEGEND_LEFT_MARGIN = 6;
	const VERTICAL_LEGEND_MAX_WIDTH_PCT = 0.70;
	const LEGEND_LINE_BULLET_WIDTH = 14;
	const LEGEND_BOX_BULLET_WIDTH = 5;
	const LEGEND_BULLET_RIGHT_PADDING = 5;
	const LEGEND_ITEM_HORIZONTAL_INTERSTICE = 6;
	const LEGEND_ITEM_VERTICAL_INTERSTICE_OFFSET = 4;
	const LEGEND_SHADOW_OPACITY = 25;
	const LEGEND_VERTICAL_SHADOW_PADDING = 3;
	const LEGEND_HORIZONTAL_SHADOW_PADDING = 2;
	const PCHART_HARD_CODED_VERTICAL_LEGEND_INTERSTICE = 5;

	protected function getDefaultColors()
	{
		return array(
			self::VALUE_COLOR_KEY => '444444',
			self::GRID_COLOR_KEY => 'CCCCCC',
			self::GRAPHIC_COLOR_KEY . '1' => '5170AE',
			self::GRAPHIC_COLOR_KEY . '2' => 'F29007',
			self::GRAPHIC_COLOR_KEY . '3' => 'CC3399',
			self::GRAPHIC_COLOR_KEY . '4' => '9933CC',
			self::GRAPHIC_COLOR_KEY . '5' => '80A033',
			self::GRAPHIC_COLOR_KEY . '6' => '246AD2'
		);
	}

	protected function initGridChart(
		$displayVerticalGridLines,
		$bulletType,
		$horizontalGraph,
		$showTicks,
		$verticalLegend
	)
	{
		$this->initpData();

		$colorIndex = 1;
		foreach($this->ordinateSeries as $column => $data)
		{
			$this->pData->setSerieWeight($column, self::DEFAULT_SERIE_WEIGHT);
			$graphicColor = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++];
			$this->pData->setPalette($column, $graphicColor);
		}

		$this->initpImage();

		// graph area coordinates
		$topLeftXValue = $this->getGridLeftMargin($horizontalGraph, $withLabel = true);
		$topLeftYValue = $this->getGridTopMargin($horizontalGraph, $verticalLegend);
		$bottomRightXValue = $this->width - $this->getGridRightMargin($horizontalGraph);
		$bottomRightYValue = $this->getGraphBottom($horizontalGraph);

		$this->pImage->setGraphArea(
			$topLeftXValue,
			$topLeftYValue,
			$bottomRightXValue,
			$bottomRightYValue
		);

		// determine how many labels need to be skipped
		$skippedLabels = 0;
		if(!$horizontalGraph)
		{
			list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
			$graphWidth = $bottomRightXValue - $topLeftXValue;
			$maxNumOfLabels = floor($graphWidth / ($abscissaMaxWidth + self::LABEL_SPACE_VERTICAL_GRAPH));

			$abscissaSeriesCount = count($this->abscissaSeries);
			if($maxNumOfLabels < $abscissaSeriesCount)
			{
				for($candidateSkippedLabels = 1 ; $candidateSkippedLabels < $abscissaSeriesCount; $candidateSkippedLabels++)
				{
					$numberOfSegments = $abscissaSeriesCount / ($candidateSkippedLabels + 1);
					$numberOfCompleteSegments = floor($numberOfSegments);

					$numberOfLabels = $numberOfCompleteSegments;
					if($numberOfSegments > $numberOfCompleteSegments)
					{
						$numberOfLabels++;
					}

					if($numberOfLabels <= $maxNumOfLabels )
					{
						$skippedLabels = $candidateSkippedLabels;
						break;
					}
				}
			}

			if($this->forceSkippedLabels
				&& $skippedLabels
				&& $skippedLabels < $this->forceSkippedLabels
				&& $abscissaSeriesCount > $this->forceSkippedLabels + 1
			)
			{
				$skippedLabels = $this->forceSkippedLabels;
			}
		}

		$ordinateAxisLength =
			$horizontalGraph ? $bottomRightXValue - $topLeftXValue : $this->getGraphHeight($horizontalGraph, $verticalLegend);

		$maxOrdinateValue = 0;
		foreach($this->ordinateSeries as $column => $data)
		{
			$currentMax = $this->pData->getMax($column);

			if($currentMax > $maxOrdinateValue)
			{
				$maxOrdinateValue = $currentMax;
			}
		}

		// rounding top scale value to the next multiple of 10
		if($maxOrdinateValue > 10)
		{
			$modTen = $maxOrdinateValue % 10;
			if($modTen) $maxOrdinateValue += 10 - $modTen;
		}

		$gridColor = $this->colors[self::GRID_COLOR_KEY];
		$this->pImage->drawScale(
			array(
				 'Mode' => SCALE_MODE_MANUAL,
				 'GridTicks' => 0,
				 'LabelSkip' => $skippedLabels,
				 'DrawXLines' => $displayVerticalGridLines,
				 'Factors' => array(ceil($maxOrdinateValue / 2)),
				 'MinDivHeight' => $ordinateAxisLength / 2,
				 'AxisAlpha' => 0,
				 'SkippedAxisAlpha' => 0,
				 'TickAlpha' => $showTicks ? self::DEFAULT_TICK_ALPHA : 0,
				 'InnerTickWidth' => self::INNER_TICK_WIDTH,
				 'OuterTickWidth' => self::OUTER_TICK_WIDTH,
				 'GridR' => $gridColor['R'],
				 'GridG' => $gridColor['G'],
				 'GridB' => $gridColor['B'],
				 'GridAlpha' => 100,
				 'ManualScale' => array(
					 0 => array(
						 'Min' => 0,
						 'Max' => $maxOrdinateValue
					 )
				 ),
				 'Pos' => $horizontalGraph ? SCALE_POS_TOPBOTTOM : SCALE_POS_LEFTRIGHT,
			)
		);

		if($this->showLegend)
		{
			switch($bulletType)
			{
				case LEGEND_FAMILY_LINE:
					$bulletWidth = self::LEGEND_LINE_BULLET_WIDTH;

					// measured using a picture editing software
					$iconOffsetAboveLabelSymmetryAxis = -2;
					break;

				case LEGEND_FAMILY_BOX:
					$bulletWidth = self::LEGEND_BOX_BULLET_WIDTH;

					// measured using a picture editing software
					$iconOffsetAboveLabelSymmetryAxis = 3;
					break;
			}

			// pChart requires two coordinates to draw the legend $legendTopLeftXValue & $legendTopLeftYValue
			// $legendTopLeftXValue = legend's left padding
			$legendTopLeftXValue = $topLeftXValue + ($verticalLegend ? self::VERTICAL_LEGEND_LEFT_MARGIN : self::HORIZONTAL_LEGEND_LEFT_MARGIN);

			// $legendTopLeftYValue = y coordinate of the top edge of the legend's icons
			// Caution :
			//  - pChart will silently add some value (see $paddingAddedByPChart) to $legendTopLeftYValue depending on multiple criterias
			//  - pChart will not take into account the size of the text. Setting $legendTopLeftYValue = 0 will crop the legend's labels
			// The following section of code determines the value of $legendTopLeftYValue while taking into account the following paremeters :
			//  - whether legend items have icons
			//  - whether icons are bigger than the legend's labels
			//  - how much colored shadow padding is required
			list($maxLogoWidth, $maxLogoHeight) = self::getMaxLogoSize(array_values($this->ordinateLogos));
			if($maxLogoHeight >= $this->legendFontSize)
			{
				$heightOfTextAboveBulletTop = 0;
				$paddingCreatedByLogo = $maxLogoHeight - $this->legendFontSize;
				$effectiveShadowPadding = $paddingCreatedByLogo < self::LEGEND_VERTICAL_SHADOW_PADDING * 2 ? self::LEGEND_VERTICAL_SHADOW_PADDING - ($paddingCreatedByLogo / 2) : 0;
			}
			else
			{
				if($maxLogoHeight)
				{
					// measured using a picture editing software
					$iconOffsetAboveLabelSymmetryAxis = 5;
				}
				$heightOfTextAboveBulletTop = $this->legendFontSize / 2 - $iconOffsetAboveLabelSymmetryAxis;
				$effectiveShadowPadding = self::LEGEND_VERTICAL_SHADOW_PADDING;
			}

			$effectiveLegendItemVerticalInterstice = $this->legendFontSize + self::LEGEND_ITEM_VERTICAL_INTERSTICE_OFFSET;
			$effectiveLegendItemHorizontalInterstice = self::LEGEND_ITEM_HORIZONTAL_INTERSTICE + self::LEGEND_HORIZONTAL_SHADOW_PADDING;

			$legendTopMargin = $verticalLegend ? self::VERTICAL_LEGEND_TOP_MARGIN : self::HORIZONTAL_LEGEND_TOP_MARGIN;
			$requiredPaddingAboveItemBullet = $legendTopMargin + $heightOfTextAboveBulletTop + $effectiveShadowPadding;

			$paddingAddedByPChart = 0;
			if($verticalLegend)
			{
				if($maxLogoHeight)
				{
					// see line 1691 of pDraw.class.php
					if($maxLogoHeight < $effectiveLegendItemVerticalInterstice)
					{
						$paddingAddedByPChart = ($effectiveLegendItemVerticalInterstice / 2) - ($maxLogoHeight / 2);
					}
				}
				else
				{
					// see line 1711 of pDraw.class.php ($Y+$IconAreaHeight/2)
					$paddingAddedByPChart = $effectiveLegendItemVerticalInterstice / 2;
				}
			}

			$legendTopLeftYValue = $paddingAddedByPChart < $requiredPaddingAboveItemBullet ? $requiredPaddingAboveItemBullet - $paddingAddedByPChart : 0;

			// add colored background to each legend item
			if(count($this->ordinateLabels) > 1)
			{
				$currentPosition = $verticalLegend ? $legendTopMargin : $legendTopLeftXValue;
				$colorIndex = 1;
				foreach($this->ordinateLabels as $metricCode => &$label)
				{
					$color = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++];

					$paddedBulletWidth = $bulletWidth;
					if(isset($this->ordinateLogos[$metricCode]))
					{
						$paddedBulletWidth = $maxLogoWidth;
					}
					$paddedBulletWidth += self::LEGEND_BULLET_RIGHT_PADDING;

					// truncate labels if required
					if($verticalLegend)
					{
						$label = $this->truncateLabel($label, ($this->width * self::VERTICAL_LEGEND_MAX_WIDTH_PCT) - $legendTopLeftXValue - $paddedBulletWidth, $this->legendFontSize);
						$this->pData->setSerieDescription($metricCode, $label);
					}

					$rectangleTopLeftXValue = ($verticalLegend ? $legendTopLeftXValue : $currentPosition) + $paddedBulletWidth - self::LEGEND_HORIZONTAL_SHADOW_PADDING;
					$rectangleTopLeftYValue = $verticalLegend ? $currentPosition : $legendTopMargin;

					list($labelWidth, $labelHeight) = $this->getTextWidthHeight($label, $this->legendFontSize);
					$legendItemWidth = $paddedBulletWidth + $labelWidth + $effectiveLegendItemHorizontalInterstice;
					$rectangleBottomRightXValue = $rectangleTopLeftXValue + $labelWidth + (self::LEGEND_HORIZONTAL_SHADOW_PADDING * 2);

					$legendItemHeight = max($maxLogoHeight, $this->legendFontSize) + ($effectiveShadowPadding * 2);
					$rectangleBottomRightYValue = $rectangleTopLeftYValue + $legendItemHeight;

					$this->pImage->drawFilledRectangle(
						$rectangleTopLeftXValue,
						$rectangleTopLeftYValue,
						$rectangleBottomRightXValue,
						$rectangleBottomRightYValue,
						array(
							'Alpha' => self::LEGEND_SHADOW_OPACITY,
							'R' => $color['R'],
							'G' => $color['G'],
							'B' => $color['B'],
						)
					);

					if($verticalLegend)
					{
						$currentPositionIncrement = max($maxLogoHeight, $effectiveLegendItemVerticalInterstice, $this->legendFontSize) + self::PCHART_HARD_CODED_VERTICAL_LEGEND_INTERSTICE;
					}
					else
					{
						$currentPositionIncrement = $legendItemWidth;
					}

					$currentPosition += $currentPositionIncrement;
				}
			}

			// draw legend
			$legendColor = $this->colors[self::VALUE_COLOR_KEY];
			$this->pImage->drawLegend(
				$legendTopLeftXValue,
				$legendTopLeftYValue,
				array(
					'Style' => LEGEND_NOBORDER,
					'FontSize' => $this->legendFontSize,
					'BoxWidth' => $bulletWidth,
					'XSpacing' => $effectiveLegendItemHorizontalInterstice, // not effective when vertical
					'Mode' => $verticalLegend ? LEGEND_VERTICAL : LEGEND_HORIZONTAL,
					'BoxHeight' => $verticalLegend ? $effectiveLegendItemVerticalInterstice : null,
					'Family' => $bulletType,
					'FontR' => $legendColor['R'],
					'FontG' => $legendColor['G'],
					'FontB' => $legendColor['B'],
				)
			);
		}
	}

	protected static function getMaxLogoSize($logoPaths)
	{
		$maxLogoWidth = 0;
		$maxLogoHeight = 0;
		foreach($logoPaths as $logoPath)
		{
			list($logoWidth, $logoHeight) = self::getLogoSize($logoPath);

			if($logoWidth > $maxLogoWidth)
			{
				$maxLogoWidth = $logoWidth;
			}
			if($logoHeight > $maxLogoHeight)
			{
				$maxLogoHeight = $logoHeight;
			}
		}

		return array($maxLogoWidth, $maxLogoHeight);
	}

	protected static function getLogoSize($logoPath)
	{
		$pathInfo = getimagesize($logoPath);
		return array($pathInfo[0], $pathInfo[1]);
	}

	protected function getGridLeftMargin($horizontalGraph, $withLabel)
	{
		$gridLeftMargin = self::LEFT_GRID_MARGIN + self::OUTER_TICK_WIDTH;

		if($withLabel)
		{
			list($maxTextWidth, $maxTextHeight) = $this->getMaximumTextWidthHeight($horizontalGraph ? $this->abscissaSeries : $this->ordinateSeries);
			$gridLeftMargin += $maxTextWidth;
		}

		return $gridLeftMargin;
	}

	protected function getGridTopMargin($horizontalGraph, $verticalLegend)
	{
		list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);

		if($horizontalGraph)
		{
			$topMargin = $ordinateMaxHeight + self::TOP_GRID_MARGIN_HORIZONTAL_GRAPH + self::OUTER_TICK_WIDTH;
		}
		else
		{
			$topMargin = $ordinateMaxHeight / 2;
		}

		if($this->showLegend && !$verticalLegend)
		{
			$topMargin += $this->getHorizontalLegendHeight();
		}

		return $topMargin;
	}

	private function getHorizontalLegendHeight()
	{
		list($maxMetricLegendWidth, $maxMetricLegendHeight) =
			$this->getMaximumTextWidthHeight(array_values($this->ordinateLabels), $this->legendFontSize);

		return $maxMetricLegendHeight + self::HORIZONTAL_LEGEND_BOTTOM_MARGIN + self::HORIZONTAL_LEGEND_TOP_MARGIN;
	}

	protected function getGraphHeight($horizontalGraph, $verticalLegend)
	{
		return $this->getGraphBottom($horizontalGraph) - $this->getGridTopMargin($horizontalGraph, $verticalLegend);
	}

	private function getGridBottomMargin($horizontalGraph)
	{
		$gridBottomMargin = self::BOTTOM_GRID_MARGIN;
		if(!$horizontalGraph)
		{
			list($abscissaMaxWidth, $abscissaMaxHeight) = $this->getMaximumTextWidthHeight($this->abscissaSeries);
			$gridBottomMargin += $abscissaMaxHeight;
		}
		return $gridBottomMargin;
	}

	protected function getGridRightMargin($horizontalGraph)
	{
		if($horizontalGraph)
		{
			// in horizontal graphs, metric values are displayed on the far right of the bar
			list($ordinateMaxWidth, $ordinateMaxHeight) = $this->getMaximumTextWidthHeight($this->ordinateSeries);
			return self::RIGHT_GRID_MARGIN_HORIZONTAL_GRAPH + $ordinateMaxWidth;
		}
		else
		{
			return 0;
		}
	}

	protected function getGraphBottom($horizontalGraph)
	{
		return $this->height - $this->getGridBottomMargin($horizontalGraph);
	}

	protected function truncateLabel($label, $labelWidthLimit, $fontSize = false)
	{
		list($truncationTextWidth, $truncationTextHeight) = $this->getTextWidthHeight(self::TRUNCATION_TEXT, $fontSize);
		list($labelWidth, $labelHeight) = $this->getTextWidthHeight($label, $fontSize);

		if($labelWidth > $labelWidthLimit)
		{
			$averageCharWidth = $labelWidth / strlen($label);
			$charsToKeep = floor(($labelWidthLimit - $truncationTextWidth) / $averageCharWidth);
			$label = substr($label, 0, $charsToKeep) . self::TRUNCATION_TEXT;
		}
		return $label;
	}

	// display min & max values
	// can not currently be used because pChart's label design is not flexible enough
	// e.g: it is not possible to remove the box border & the square icon
	// it would require modifying pChart code base which we try to avoid
	// see http://dev.piwik.org/trac/ticket/3396
//	protected function displayMinMaxValues()
//	{
//		if($displayMinMax)
//		{
//			// when plotting multiple metrics, display min & max on both series
//			// to fix: in vertical bars, labels are hidden when multiple metrics are plotted, hence the restriction on count($this->ordinateSeries) == 1
//			if($this->multipleMetrics && count($this->ordinateSeries) == 1)
//			{
//				$colorIndex = 1;
//				foreach($this->ordinateSeries as $column => $data)
//				{
//					$color = $this->colors[self::GRAPHIC_COLOR_KEY . $colorIndex++];
//
//					$this->pImage->writeLabel(
//						$column,
//						self::locateMinMaxValue($data),
//						$Format = array(
//							'NoTitle' => true,
//							'DrawPoint' => false,
//							'DrawSerieColor' => true,
//							'TitleMode' => LABEL_TITLE_NOBACKGROUND,
//							'GradientStartR' => $color['R'],
//							'GradientStartG' => $color['G'],
//							'GradientStartB' => $color['B'],
//							'GradientEndR' => 255,
//							'GradientEndG' => 255,
//							'GradientEndB' => 255,
//							'BoxWidth' => 0,
//							'VerticalMargin' => 9,
//							'HorizontalMargin' => 7,
//						)
//					);
//				}
//			}
//			else
//			{
//				// display only one min & max label
//			}
//		}
//	}

//	protected static function locateMinMaxValue($data)
//	{
//		$firstValue = $data[0];
//		$minValue = $firstValue;
//		$minValueIndex = 0;
//		$maxValue = $firstValue;
//		$maxValueIndex = 0;
//		foreach($data as $index => $value)
//		{
//			if($value > $maxValue)
//			{
//				$maxValue = $value;
//				$maxValueIndex = $index;
//			}
//
//			if($value < $minValue)
//			{
//				$minValue = $value;
//				$minValueIndex = $index;
//			}
//		}
//
//		return array($minValueIndex, $maxValueIndex);
//	}
}